import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { SelectQueryBuilder, getConnection, Repository } from 'typeorm';
import { LoginDto } from '../controllers/dto/login.dto';
import { UserEntity } from '../entities/user.entity';
import { isMobilePhone } from 'class-validator';
import { ToolsService } from '@src/modules/shared/services/tools/tools.service';
import { ICurrentUserType } from '@src/decorators/current.user';
import { LoginVo } from '../controllers/vo/login.vo';
import { InjectRepository } from '@nestjs/typeorm';
import { UserTokenEntity } from '../entities/user.token.entity';
import { ConfigService, InjectConfig } from 'nestjs-config';
import moment from 'moment';

// 使用 @Injectable() 装饰的类就是一个 Provider，装饰器方法会优先于类被解析执行。
@Injectable()
export class LoginService {
  private logger: Logger = new Logger(LoginService.name);
  constructor(
    private readonly toolsService: ToolsService,
    @InjectRepository(UserTokenEntity)
    private readonly userTokenRepository: Repository<UserTokenEntity>,
    @InjectConfig()
    private readonly configService: ConfigService,
  ) {}

  /**
   * @description: 用户登录
   */
  async adminLogin(loginDto: LoginDto): Promise<LoginVo> {
    try {
      const { username, password } = loginDto;
      type TypeAccountFindResult = Extract<UserEntity, ICurrentUserType> | undefined;
      let findUser: TypeAccountFindResult;
      const queryBuilder = this.queryLoginBuilder;
      // 根据手机号查询
      if (isMobilePhone(username, 'zh-CN')) {
        findUser = await queryBuilder
          .where('(user.mobile = :mobile)', { mobile: username })
          .getRawOne();
      } else {
        // 根据用户名查询
        findUser = await queryBuilder
          .where('(user.username = :username)', { username })
          .getRawOne();
      }
      if (
        findUser &&
        findUser.password &&
        this.toolsService.checkPassword(password, findUser.password)
      ) {
        // 生成token存储到token表中并且返回给前端
        const token = this.toolsService.uuidToken;
        const tokenExpire: number = this.configService.get('user.tokenExpire');
        const { user_id, username, mobile, isSuper } = this.filterAccountField(findUser);
        const userToken = {
          user_id,
          username,
          mobile,
          isSuper,
          token,
          // 设置token失效时间
          expireTime: moment().add(tokenExpire, 'day').format('YYYY-MM-DD HH:mm:ss'),
        };

        // 先判断之前是否有记录，有记录就更新，没记录就创建
        const userTokenResult: Pick<UserTokenEntity, 'user_token_id'> | undefined =
          await this.userTokenRepository.findOne({
            where: { user_id: user_id },
            select: ['user_token_id'],
          });
        if (userTokenResult?.user_token_id) {
          // 更新
          await this.userTokenRepository.update(
            { user_token_id: userTokenResult.user_token_id },
            userToken,
          );
        } else {
          // 创建
          const userTokenSave: UserTokenEntity = this.userTokenRepository.create(userToken);
          await this.userTokenRepository.save(userTokenSave);
        }
        return {
          username,
          isSuper,
          token,
        };
      } else {
        throw new HttpException('用户名或密码错误', HttpStatus.OK);
      }
    } catch (e) {
      this.logger.error('用户名或密码错误', e);
      throw new HttpException('用户名或密码错误', HttpStatus.OK);
    }
  }

  /**
   * @description: 公共的查询部分
   */
  private get queryLoginBuilder(): SelectQueryBuilder<UserEntity> {
    return getConnection()
      .createQueryBuilder(UserEntity, 'user')
      .select('user.user_id', 'user_id')
      .addSelect('user.username', 'username')
      .addSelect('user.mobile', 'mobile')
      .addSelect('user.isSuper', 'isSuper')
      .addSelect('user.password', 'password');
  }

  /**
   * @description: 过滤字段
   */
  private filterAccountField(accountInfo: ICurrentUserType): ICurrentUserType {
    const { mobile } = accountInfo;
    const _mobile = isMobilePhone(mobile, 'zh-CN') ? mobile : '';
    return {
      ...accountInfo,
      mobile: _mobile,
    };
  }
}
